﻿using ScriptEngine.Script.Exceptions;
using ScriptEngine.Script.Extension;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;

namespace ScriptEngine.Script.Executor
{
	public abstract class AbstractScriptExecutor : IScriptExecutor
	{
		/// <summary>
		///  存取已经注入到该脚本环境的，方法/属性等信息的数据源
		/// </summary>
		public List<CodeTipPojo> Datasource { get; } = new List<CodeTipPojo>();

		/// <summary>
		/// 正在处于监听模式下的函数
		/// </summary>
		private HashSet<string> _listenerFuncSet = new HashSet<string>();

		/// <summary>
		/// 函数引入数量
		/// </summary>
		public int FuncRefCount
		{
			get
			{
				lock (locker)
				{
					return _listenerFuncSet.Count;
				}
			}
		}

		// 线程锁
		protected readonly object locker = new object();

		// 条件变量
		private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false);

		// 脚本引擎的运行状态
		private volatile ScriptEngineStatus _status = ScriptEngineStatus.EXITED;

		// 脚本状态切换事件
		public event Action<ScriptEngineStatus> OnScriptEngineStatusChangeEvent;

		/// <summary>
		/// 脚本是否处于运作状态
		/// </summary>
		public bool IsRunning => EngineStatus != ScriptEngineStatus.EXITED;


		/// <summary>
		/// 脚本是否处于准备运行状态
		/// </summary>
		public ScriptEngineStatus EngineStatus
		{
			get => _status;
			protected set
			{
				lock (locker)
				{
					if (_status != value)
					{
						_status = value;
						EmitScriptEngineStatusChangeEvent();
					}
				}
			}
		}

		/// <summary>
		/// 脚本环境添加一个函数引用(一直占有引用则脚本不会退出,同名函数重复添加只会占有一次)
		/// </summary>
		/// <param name="funcName">函数名</param>
		public void IncFuncRef(string funcName)
		{
			lock (locker)
			{
				_listenerFuncSet.Add(funcName);
			}
		}

		/// <summary>
		/// 脚本环境减少一个函数引用
		/// </summary>
		/// <param name="funcName">函数名</param>
		public void DecFuncRef(string funcName)
		{
			lock (locker)
			{
				_listenerFuncSet.Remove(funcName);
				if (_listenerFuncSet.Count <= 0)
				{
					_resetEvent.Set();
				}
			}
		}

		/// <summary>
		/// 触发脚本状态改变事件
		/// </summary>
		protected void EmitScriptEngineStatusChangeEvent() => OnScriptEngineStatusChangeEvent?.Invoke(EngineStatus);

		/// <summary>
		/// 处理事件循环，此处当前只等待函数引用为空
		/// </summary>
		protected virtual void HandleEventLoop()
		{
			while (FuncRefCount > 0)
			{
				_resetEvent.WaitOne(50);
			}
		}

		/// <summary>
		/// 检测脚本是否正在运行，否则抛出异常
		/// </summary>
		protected void AssertEngineIsRunning()
		{
			if (EngineStatus != ScriptEngineStatus.RUNNING)
			{
				throw new ScriptException("script not running");
			}
		}

		/// <summary>
		/// 扫描指定对象的属性和方法，将带有相关特性的注入脚本环境中
		/// </summary>
		/// <param name="obj">待扫描对象</param>
		public virtual void RegisterFuncAndObjToEnv(object obj)
		{
			// 获取需要注入的属性
			foreach (PropertyInfo propertyInfo in obj.GetType().GetProperties())
			{
				object[] attributes = propertyInfo.GetCustomAttributes(typeof(ScriptCallCsharp), true);
				if (attributes != null && attributes.Length > 0)
				{
					ScriptCallCsharp scriptCallCsharp = (ScriptCallCsharp)attributes[0];
					string name = scriptCallCsharp.Name;
					if (string.IsNullOrWhiteSpace(name)) name = propertyInfo.Name;
					object instance = propertyInfo.GetValue(obj);
					// 获取该属性是否有被废弃注解标注
					var obsoleteAttr = propertyInfo.GetCustomAttribute<ObsoleteAttribute>();
					// 获取该属性是否可用于代码提示
					bool canTip = propertyInfo.GetCustomAttribute<CodeNotTip>() == null;
					// 设置数据源
					Datasource.Add(new CodeTipPojo(obj, instance, scriptCallCsharp, name, obsoleteAttr, canTip));
				}
			}

			/***********注册方法***************/
			foreach (var methodInfo in obj.GetType().GetMethods())
			{
				object[] attributes = methodInfo.GetCustomAttributes(typeof(ScriptCallCsharp), true);
				if (attributes != null && attributes.Length != 0)
				{
					ScriptCallCsharp scriptCallCsharp = attributes[0] as ScriptCallCsharp;
					string funcName = scriptCallCsharp.Name;
					// 未使用自定义方法名时则使用原本的方法名，首字母改成小写
					if (string.IsNullOrEmpty(funcName))
					{
						funcName = methodInfo.Name.Substring(0, 1).ToLower() + methodInfo.Name.Substring(1);
					}
					// 获取该方法是否有被废弃注解标注
					var obsoleteAttr = methodInfo.GetCustomAttribute<ObsoleteAttribute>();
					// 获取该方法是否可以同于代码提示
					bool canTip = methodInfo.GetCustomAttribute<CodeNotTip>() == null;
					// 设置数据源
					Datasource.Add(new CodeTipPojo(obj, methodInfo, scriptCallCsharp, funcName, obsoleteAttr, canTip));
				}
			}
		}
		public abstract T ExeFunc<T>(string funcName, params object[] args);
		public abstract void ExecFunc(string funcName, params object[] args);
		public abstract void RunWithCode(string code);
		public abstract void RunWithPath(string filepath);

		/// <summary>
		/// 从脚本环境中获取对象
		/// </summary>
		/// <param name="symbol">符号</param>
		/// <returns>获取到的对象</returns>
		public abstract object GetObject(string symbol);

		public virtual void Exit()
		{
			lock (locker)
			{
				if (!IsRunning)
				{
					return;
				}

				_listenerFuncSet.Clear();
				_resetEvent.Set();
				EngineStatus = ScriptEngineStatus.BEFORE_EXIT;
			}
		}
	}
}
